/**
 * Parser for AMFM programs outputs AST.
 *
 * @author talgiat
 */
grammar AmFm;
options {
 output=AST;
}
tokens {
  PROGRAM;
  DEF;
  NOTEUNIT;
  NOTE;
}
@header{
/*
 * Copyright 2009, Team Mondegreen:
 * Brian Hsieh - bh2181@columbia.edu
 * Eugene Yao - eyy2102@columbia.edu
 * Jake Amonette - jaa2149@columbia.edu
 * Tal Giat - tg2235@colubia.edu
 * 
 * This file is part of AMFM (A Minimal Fractal Music Language), a language created for
 * fractal music as part of the Programming Languages and Translator class at Columbia university.
 *
 * For more details go to: http://code.google.com/p/amfm/
 * 
 * AMFM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * at your option any later version.
 * 
 * AMFM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with AMFM.  If not, see <http://www.gnu.org/licenses/>. 
 */ 
package org.mondegreen.amfm.compiler;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
}
@lexer::header {
/*
 * Copyright 2009, Team Mondegreen:
 * Brian Hsieh - bh2181@columbia.edu
 * Eugene Yao - eyy2102@columbia.edu
 * Jake Amonette - jaa2149@columbia.edu
 * Tal Giat - tg2235@colubia.edu
 * 
 * This file is part of AMFM (A Minimal Fractal Music Language), a language created for
 * fractal music as part of the Programming Languages and Translator class at Columbia university.
 *
 * For more details go to: http://code.google.com/p/amfm/
 * 
 * AMFM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * at your option any later version.
 * 
 * AMFM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with AMFM.  If not, see <http://www.gnu.org/licenses/>. 
 */
 
package org.mondegreen.amfm.compiler;
}
@lexer::members { // These methods are defined twice once for the lexer and once for the parser

/** An error manager to register all compilation errors to */
private AmFmErrorManager errorManager;

/**
 * Sets the error manager this must be called before lexing or a NullPointerException will be throw in case of
 * semantic error because the code will try to add the error message to the manager. 
 * @param errorManager the compilation error manager 
 */
public void setErrorManager(AmFmErrorManager errorManager) {
  this.errorManager = errorManager;
}

/**
 * Overriding antlr's method so we can redirect (and register) its error messages to AmFmErrorManager 
 * @param msg the lexing error message to capture
 */
public void emitErrorMessage(String msg) {
  errorManager.addError(AmFmErrorCode.ANTLR_ERROR, msg);
}

}
@members {
/** The maximal number of function calls in the start function (due to midi 16 channels limitation) */
private static final int MAX_NUM_OF_FUNCTION_CALLS = 16;

/** All used (referenced) seeds */ 
private Set<String> usedSeeds = new HashSet<String>();

/** The number of functions actually called in the program */
private int numOfFunctionCalls = 0;

/**
 * A flag that indicated did we start at the program rule so we won't do semantic validation 
 * check in rules if we didn't (so we can test grammar rules independently)
 */
private boolean startedFromProgramRule = false;

/** List of all seeds names (i.e. simple symbol table) */
private Map<String,Boolean> definedSeeds = new HashMap<String,Boolean>();

/** An error manager to register all compilation errors to */
private AmFmErrorManager errorManager;

/** Flag for marking rest only seeds (invalid)*/
private boolean seedOnlyRests; 

/**
 * Sets the error manager this must be called before parsing or a NullPointerException will be throw in case of
 * semantic error because the code will try to add the error message to the manager. 
 * @param errorManager the compilation error manager 
 */
public void setErrorManager(AmFmErrorManager errorManager) {
  this.errorManager = errorManager;
}

public Set<String> getUsedSeeds() { 
  return usedSeeds; 
}

/**
 * Overriding antlr's method so we can redirect (and register) its error messages to AmFmErrorManager 
 * @param msg the parsing error message to capture
 */
public void emitErrorMessage(String msg) {
  errorManager.addError(AmFmErrorCode.ANTLR_ERROR, msg);
}

} // End members


// AMFM program 
program
@init {
// Turn on flag to indicate we parse a full program 
startedFromProgramRule = true;
}
      : seed+ start_func
      {
        if (numOfFunctionCalls > MAX_NUM_OF_FUNCTION_CALLS) {
          errorManager.addError(AmFmErrorCode.TOO_MANY_FUNCTION_CALLS, MAX_NUM_OF_FUNCTION_CALLS, numOfFunctionCalls);
        }
      } 
      -> ^(PROGRAM seed+ start_func);

// Seed rules
seed
@init {
// Start of the seed we assume it only rests till proven otherwise
seedOnlyRests = true;
}  
: 'seed' ID '{' INSTRUMENT ';' note_unit+ '}' 
        { 
          if (startedFromProgramRule) { // Do validation only if we parse a whole program
	          if (definedSeeds.get($ID.text) != null) { // Seed is defined twice
	            errorManager.addError(AmFmErrorCode.REDIFINED_SEED, $ID.line, $ID.text);
	          } else { // Seed is ok - add its name to seeds lookup table
	            definedSeeds.put($ID.text,seedOnlyRests);
	          }
          }
        } 
        -> ^(ID INSTRUMENT note_unit+); 
note_unit : note  ( ',' duration ( ',' volume)? )? ';'
          { seedOnlyRests = false; } 
          -> ^(NOTEUNIT ^(NOTE note) (duration volume?)?) 
          | REST ( ',' duration)? ';' -> ^(NOTEUNIT REST duration?) 
          ;
duration: FRACTION | number;
volume  : VOLUME_LEVELS_MINUS_F | 'F';
note :  note_letter octave? ; 
note_letter: NOTELETTERS_WITH_ACCIDENTAL_MINUS_F | 'F'ACCIDENTAL?;
octave: ONE_TO_NINE;

// Start function
start_func: START '{' function* '}' -> ^(START function*);
function: (play | ss_func) ';'!
      { 
        numOfFunctionCalls++; 
      };
play: PLAY '(' func_params[$PLAY.text] ')' -> ^(PLAY func_params);
ss_func: ss_func_name '(' ss_func_params[$ss_func_name.text] ')' -> ^(ss_func_name ss_func_params);
ss_func_name: (SS_PITCH | SS_VOLUME | SS_RHYTHM | SS_PITCH_VOLUME | SS_PITCH_RHYTHM | SS_RHYTHM_VOLUME | SS_ALL);
ss_func_params[String func_name]: func_params[$func_name] ','! BOOLEAN;
func_params[String func_name]: ID ','! start_bar ','! num_of_bars 
            { 
              if (startedFromProgramRule) { // Do validation only if we parse a whole program
	              // Check if seed was defind
	              if (definedSeeds.get($ID.text) == null) {
	                errorManager.addError(AmFmErrorCode.REFERRING_TO_UNDEFINED_SEED, $ID.line, $ID.text, $func_name);
	              } else if(definedSeeds.get($ID.text)) {
	                errorManager.addError(AmFmErrorCode.REFERRING_TO_ALL_RESTS_SEED, $ID.line, $ID.text, $func_name); 
	              } else { // put seed in the set of used seeds
	                usedSeeds.add($ID.text);
	              }
	              
              }
            }; 
start_bar: number ;
num_of_bars: number ;
number  : ONE_TO_NINE | TEN_AND_UP;
 
// Tokens

// Functions
START: 'start';
PLAY: 'play';
SS_PITCH : 'SSpitch';
SS_VOLUME: 'SSvolume';
SS_RHYTHM: 'SSrhythm';
SS_PITCH_VOLUME: 'SSpitch_volume';
SS_PITCH_RHYTHM: 'SSpitch_rhythm';
SS_RHYTHM_VOLUME: 'SSrhythm_volume';
SS_ALL: 'SSall';

COMMENT : '//'(~('\n'|'\r'))*('\n'|'\r'('\n')?)? {skip();};

// Notes
INSTRUMENT : 'PIANO' | 'VIOLIN' | 'CELLO' | 'TRUMPET' | 'VIOLA' | 'FLUTE'; 
NOTELETTERS_WITH_ACCIDENTAL_MINUS_F : ('A'..'E' | 'G')ACCIDENTAL? | 'F'ACCIDENTAL ;
REST: 'R';
VOLUME_LEVELS_MINUS_F : 'PPP' | 'PP' | 'P' | 'MP' | 'MF' | 'FF' | 'FFF'; 
fragment
ACCIDENTAL : ('#' | 'b' | 'bb' | 'x'); 
BOOLEAN : 'TRUE' | 'FALSE';
ID : ('a'..'z')('a'..'z'|'A'..'Z'|'0'..'9')* ;
FRACTION : (ONE_TO_NINE|TEN_AND_UP)'/'(ONE_TO_NINE|TEN_AND_UP);
ONE_TO_NINE: '1'..'9';   
TEN_AND_UP: ONE_TO_NINE('0'..'9')+;
WS : (' '|'\t' | '\n' | '\r')+ {skip();};